package br.com.intelsys.security.itoken;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import br.com.intelsys.basics.dao.DaoException;
import br.com.intelsys.security.itoken.IToken.Status;

@Service
public class ITokenService {
	private final ITokenDao iTokenDao;

	@Autowired
	public ITokenService(ITokenDao iTokenDao) {
		this.iTokenDao = iTokenDao;
	}

	public IToken createUniqueIToken(int codITokenType, int codOwner, int codAux, int usesMax, int daysToExpire) {
		IToken iToken = getUsableITokenConstraint(codITokenType, codOwner, codAux);
		if (iToken != null) {
			return iToken;
		}
		return createIToken(codITokenType, codOwner, codAux, usesMax, daysToExpire);
	}

	public IToken createUniqueReplacableIToken(int codITokenType, int codOwner, int codAux, int usesMax, int daysToExpire) {
		revokeUsableITokenConstraint(codITokenType, codOwner, codAux);
		return createUniqueIToken(codITokenType, codOwner, codAux, usesMax, daysToExpire);
	}

	public IToken createIToken(int codITokenType, int codOwner, int codAux, int usesMax, int daysToExpire) {
		Date dateExpire;
		if (daysToExpire > 0) {
			Calendar cal = new GregorianCalendar();
			cal.add(Calendar.DAY_OF_MONTH, daysToExpire);
			dateExpire = cal.getTime();
		} else {
			dateExpire = null;
		}
		IToken t = iTokenDao.getIToken(codITokenType, codOwner, codAux, usesMax, dateExpire);
		try {
			iTokenDao.saveIToken(t);
		} catch (DaoException e) {
			t = null;
		}
		return t;
	}

	public IToken getUsableITokenConstraint(int codITokenType, int codOwner, int codAux) {
		List<IToken> iTokenList = iTokenDao.getITokenListFromTypeOwnerAuxAndStatusOn(codITokenType, codOwner, codAux);
		if (iTokenList != null) {
			for (IToken iToken : iTokenList) {
				try {
					checkIToken(iToken, codAux);
					return iToken;
				} catch (ITokenException e) {
				}
			}
		}
		return null;
	}

	public IToken revokeUsableITokenConstraint(int codITokenType, int codOwner, int codAux) {
		List<IToken> iTokenList = iTokenDao.getITokenListFromTypeOwnerAuxAndStatusOn(codITokenType, codOwner, codAux);
		if (iTokenList != null) {
			for (IToken iToken : iTokenList) {
				try {
					revokeToken(iToken);
				} catch (ITokenException e) {
				}
			}
		}
		return null;
	}

	public IToken getTokenWithoutUse(String token, int codAux) throws ITokenNotFoundException, ITokenCodAuxNotMatchException,
			ITokenViolationException {
		IToken t = iTokenDao.getIToken(token);
		checkIToken(t, codAux);
		return t;
	}

	public IToken useToken(String encodedToken) throws ITokenNotFoundException, ITokenCodAuxNotMatchException, ITokenViolationException {
		TokenEncoder tokenEncoder = getDecodedToken(encodedToken);
		return useToken(tokenEncoder.getToken(), tokenEncoder.getCodAux());
	}

	public IToken useToken(String token, int codAux) throws ITokenNotFoundException, ITokenCodAuxNotMatchException,
			ITokenViolationException {
		IToken t = getTokenWithoutUse(token, codAux);
		((ITokenHiber) t).setUses(t.getUses() + 1);
		((ITokenHiber) t).setDateLastUse(new GregorianCalendar().getTime());
		try {
			if (!isUseMaxAllowable(t)) {
				((ITokenHiber) t).setStatus(Status.OFF);
			}
			iTokenDao.saveIToken(t);
			return t;
		} catch (DaoException e) {
			throw new ITokenError("Erro ao usar um Token", e);
		}
	}

	public void revokeEncodedToken(String encodedToken) throws ITokenNotFoundException {
		revokeToken(iTokenDao.getIToken(getDecodedToken(encodedToken).getToken()));
	}

	public void revokeToken(String token) throws ITokenNotFoundException {
		revokeToken(iTokenDao.getIToken(token));
	}

	public void revokeToken(IToken iToken) throws ITokenNotFoundException {
		ITokenHiber t = (ITokenHiber) iToken;
		if (t != null) {
			t.setStatus(Status.OFF);
			try {
				iTokenDao.saveIToken(t);
			} catch (DaoException e) {
				throw new ITokenError("Erro ao salvar o token durante revoke", e);
			}
		} else {
			throw new ITokenNotFoundException();
		}
	}

	public boolean isITokenUsable(String token, int codAux) {
		try {
			checkIToken(iTokenDao.getIToken(token), codAux);
			return true;
		} catch (ITokenException e) {
			return false;
		}
	}

	private void checkIToken(IToken t, int codAux) throws ITokenDateViolationException, ITokenStatusViolationException,
			ITokenUsesMaxViolationException, ITokenCodAuxNotMatchException, ITokenNotFoundException {
		if (t != null) {
			if (t.getCodAux() == codAux || t.getCodAux() == 0) {
				if (t.getStatus().equals(Status.ON)) {
					if (isUseMaxAllowable(t)) {
						if (!(t.getDateExpires() == null || t.getDateExpires().after(new GregorianCalendar().getTime()))) {
							revokeToken(t);
							throw new ITokenDateViolationException();
						}
					} else {
						revokeToken(t);
						throw new ITokenUsesMaxViolationException();
					}
				} else {
					throw new ITokenStatusViolationException();
				}
			} else {
				throw new ITokenCodAuxNotMatchException();
			}
		} else {
			throw new ITokenNotFoundException();
		}
	}

	private boolean isUseMaxAllowable(IToken t) {
		return t.getUses() < t.getUsesMax() || t.getUsesMax() == 0;
	}

	public TokenEncoder getDecodedToken(String encodedToken) throws ITokenNotFoundException {
		return new TokenEncoder(encodedToken);
	}

}
